using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;

namespace Interceptor.Core.Objects
{
	internal class TypeBuilderContext
	{
		public ModuleBuilder Module { get; set; }
		public TypeBuilder Builder { get; set; }
		public AssemblyBuilder Assembly { get; set; }
			
		private readonly Dictionary<string, FieldBuilder> _fields = new Dictionary<string, FieldBuilder>();
		public static TypeBuilderContext DefineType(string name)
		{
			var buildContext = new TypeBuilderContext();
			var asmName = new AssemblyName("assembly_" + Helpers.GenerateNewUniqueIdentiricator());
#if SAVE_TO_FILE
			var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.RunAndSave);
#else
			var asm = AppDomain.CurrentDomain.DefineDynamicAssembly(asmName, AssemblyBuilderAccess.Run);
#endif
			buildContext.Assembly = asm;

#if SAVE_TO_FILE
			buildContext.Module = buildContext.Assembly.DefineDynamicModule(buildContext.Assembly.GetName().Name, buildContext.Assembly.GetName().Name + ".dll");
#else
			buildContext.Module = buildContext.Assembly.DefineDynamicModule(buildContext.Assembly.GetName().Name);
#endif

			buildContext.Builder = buildContext.Module.DefineType(name, TypeAttributes.Public | TypeAttributes.AutoClass | TypeAttributes.AnsiClass | TypeAttributes.BeforeFieldInit);
			return buildContext;
		}
		public void DefineInterfaceImplementation(params Type[] interfaces)
		{
			foreach (var intface in interfaces)
				Builder.AddInterfaceImplementation(intface);
		}
		public FieldBuilder Field(string name)
		{
			return _fields[name];
		}
		public void DefinePrivateField( string fieldName, Type fieldType)
		{
			AddField(fieldName, Builder.DefineField(fieldName, fieldType, FieldAttributes.Private));
		}
		public void DefineConstructor(Type[] parameters, Action<TypeBuilderContext, ILGenerator> ctorBody)
		{
			var ctor = Builder.DefineConstructor(MethodAttributes.Public, CallingConventions.Standard
			                                     , parameters);
			var ctrIL = ctor.GetILGenerator();
			ctorBody(this, ctrIL);
		}
		private MethodInfo DefineVirtualMethodInfo(string name, Type returnType, Type[] parameters, Action<TypeBuilderContext, ILGenerator> methodBody)
		{
			var method = Builder.DefineMethod(name,
			                                  MethodAttributes.Public | MethodAttributes.HideBySig | MethodAttributes.NewSlot
			                                  | MethodAttributes.Virtual,
			                                  returnType,
			                                  parameters);

			var methIL = method.GetILGenerator();
			methodBody(this, methIL);
			return method;
		}
		public void DefineVirtualMethod(string name,Type returnType, Type[] parameters, Action<TypeBuilderContext, ILGenerator> methodBody)
		{
			DefineVirtualMethodInfo(name, returnType, parameters, methodBody);
		}
		public void Override(MethodInfo methodToOverride, Action<TypeBuilderContext,ILGenerator> methodBody)
		{
			var method = DefineVirtualMethodInfo(methodToOverride.Name, methodToOverride.ReturnType,
			                                     methodToOverride.GetParameters().Select(x => x.ParameterType).ToArray()
			                                     , methodBody);
			Builder.DefineMethodOverride(method, methodToOverride);
		}
		private void AddField(string fieldName,FieldBuilder fieldBuilder)
		{
			_fields.Add(fieldName, fieldBuilder);
		}

		internal Type CompileType()
		{
			return Builder.CreateType();
		}
	}
}